package com.rao.gmall.order.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rao.gmall.common.constant.RedisConst;
import com.rao.gmall.feign.cart.CartFeignClient;
import com.rao.gmall.feign.product.SkuInfoFeignClient;
import com.rao.gmall.feign.ware.WareFeignClient;
import com.rao.gmall.model.enums.OrderStatus;
import com.rao.gmall.model.enums.ProcessStatus;
import com.rao.gmall.model.order.OrderDetail;
import com.rao.gmall.model.order.OrderInfo;
import com.rao.gmall.mq.RabbitConst;
import com.rao.gmall.order.config.OrderProperties;
import com.rao.gmall.order.mapper.OrderDetailMapper;
import com.rao.gmall.order.mapper.OrderInfoMapper;
import com.rao.gmall.order.service.OrderInfoService;
import io.swagger.models.auth.In;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;


/**
 * @author nzmxfan
 * @create 2021-08-20-19:38
 */
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    SkuInfoFeignClient skuInfoFeignClient;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    WareFeignClient wareFeignClient;

    @Autowired
    OrderProperties orderProperties;

    @Autowired
    OrderInfoMapper orderInfoMapper;

    @Autowired
    OrderDetailMapper orderDetailMapper;

    @Autowired
    CartFeignClient cartFeignClient;

    @Autowired
    RabbitTemplate rabbitTemplate;


    /**
     * 验证 tradeNo 是否一致，无论是否一致，都删除
     *
     * @param userId
     * @param tradeNo
     * @return
     */
    @Override
    public boolean checkToken(String userId, String tradeNo) {

        // 原子性验证令牌
        // 前端带请求进来，拿到令牌就删除
        String script = "if redis.call('get', KEYS[1]) == ARGV[1]" +
                "then return redis.call('del', KEYS[1]) else return 0 end";
        Long execute = redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Arrays.asList(RedisConst.USER_UNREPEAT_TOKEN + userId),
                tradeNo);
        if (!StringUtils.isEmpty(tradeNo) && execute.intValue() != 0){
            // 验证令牌成功
            return true;
        }

        return false;
    }

    @Override
    public List<String> checkSkuPriceAndStock(List<OrderDetail> orderDetailList) {

        // 集合保存后续操作的所有异步任务
        List<CompletableFuture> futures = new ArrayList<>();

        // add 不用担心导致线程安全问题
        List<String> errMsg = new ArrayList<>();

        if (!CollectionUtils.isEmpty(orderDetailList)){
            for (OrderDetail orderDetail : orderDetailList) {
                // 获取所有需要购买的商品的 skuId、商品名、商品数量、最新价格
                Long skuId = orderDetail.getSkuId();
                String skuName = orderDetail.getSkuName();
                Integer skuNum = orderDetail.getSkuNum();
                // 1.验价
                CompletableFuture<Void> PriceFuture = CompletableFuture.runAsync(() -> {
                    // 根据 skuId 获取最新价格
                    BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
                    // 获取所有需要购买的商品的订单价格
                    BigDecimal orderPrice = orderDetail.getOrderPrice();
                    if (!orderPrice.equals(skuPrice)) {
                        // 价格不匹配
                        errMsg.add(skuName + "：价格发生变化，请刷新确认");
                    }
                }, executor);

                // 2.验库存
                CompletableFuture<Void> stockFuture = CompletableFuture.runAsync(() -> {
                    // 获取库存
                    String stock = wareFeignClient.hasStock(skuId, skuNum);
                    if (stock.equals("0")) {
                        // 库存不足
                        errMsg.add(skuName + "库存不足");
                    }
                }, executor);

                futures.add(PriceFuture);
                futures.add(stockFuture);
            }
        }

        // 以前收集到的所有异步任务全部结束，才能算结束
        CompletableFuture<?>[] futureArray = new CompletableFuture[futures.size()];
        for (int i = 0; i < futures.size(); i++) {
            futureArray[i] = futures.get(i);
        }

        CompletableFuture.allOf(futureArray).join();

        return errMsg;
    }

    /**
     * 创建订单
     *  1.创建订单
     *  2.验证订单状态（通过 rabbitmq 设置设置队列，使所有队列等待 30min，然后根据订单 id 验证是否支付）
     *      2.1 未支付，30min 后自动关单
     *      2.2 已支付，更改订单状态
     * @param orderInfo
     * @return
     */
    @Transactional
    @Override
    public OrderInfo createOrder(OrderInfo orderInfo) {
        // 1.保存订单信息
        orderInfo.sumTotalAmount();
        // 设置订单状态
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        //用户id，controller透传过来的已经设置好了

        //out_trade_no； 和支付宝对接的唯一流水号
        //给支付宝给一个你订单的唯一标识（对外流水号），问支付宝某个单是否支付成功。
        //订单id（对内流水号）
        //生成的对外流水号
        orderInfo.setOutTradeNo("ATGUIGU" + System.currentTimeMillis() + new Random().nextInt(10000));
        // 订单创建时间
        orderInfo.setCreateTime(new Date());
        // expire_time 过期时间。模拟 properties.getTtl()
        // 下一次要支付这个订单之前，判断一下，这个订单如果是未支付而且过期了，就不给支付
        Calendar instance = Calendar.getInstance();
        // 当前时间 + 30min
        // 通过配置文件配置过期时间
        // 过期时间更改，必须删除原队列，使队列重新生成才能生效
        instance.add(Calendar.MINUTE, orderProperties.getTtl());
        Date time = instance.getTime();
        orderInfo.setExpireTime(time);
        // process_status
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        // 设置图片
        List<OrderDetail> detailList = orderInfo.getOrderDetailList();
        orderInfo.setImgUrl(detailList.get(0).getImgUrl());

        // 向数据库保存订单基本信息
        orderInfoMapper.insert(orderInfo);

        // 保存订单商品的详细信息（批量保存，执行一次数据库）
        for (OrderDetail orderDetail : detailList) {
            // 完善购买的商品信息（将其与订单表关联）
            Long id = orderInfo.getId();
            orderDetail.setOrderId(id);
            orderDetail.setCreateTime(new Date());
        }
        // 批量添加商品到数据库
        orderDetailMapper.saveBatch(detailList);

        // 订单创建完成之后，从购物车中删除商品
        afterOrderCreated(orderInfo);

        return orderInfo;
    }

    // 是超时的订单，我们就关单
    // 秒 分 时  日 月 周
    //

    /**
     * 方案1：使用定时清理数据库方式清理 （ @Scheduled(cron = "0 0/30 * * * *") ）
     *      缺点：时间延迟较大，可能存在 59min 的任务
     *
     * 方案2：使用自定义定时任务方式清理
     *      缺点：宕机无法之后，无法处理堆积任务
     *
     * 方案3：定时清理数据库与结果自定义定时任务
     *
     * 方案4：使用 rabbitmq 设置队列过期时间，然后根据支付状态判断是否清除
     *
     * @param id
     */
    @Override
    public void closeOrder(Long id) {
        // 去数据库修改订单状态
        // 判断如果当前订单是UPAID
        // 扫描所有未关闭的订单
        OrderInfo orderInfo = orderInfoMapper.selectById(id);
        if (orderInfo.getOrderStatus().equals(OrderStatus.UNPAID.name())){
            String name = OrderStatus.CLOSED.name();
            orderInfoMapper.updateOrderStatus(id, name);
        }

    }

    @Override
    public void changeOrderStatusPayed(String outTradeNo, String orderStatus) {
        orderInfoMapper.changeOrderStatusPayed(outTradeNo, orderStatus);
    }


    /**
     * 删除生成订单的商品
     * @param orderInfo
     */
    private void afterOrderCreated(OrderInfo orderInfo) {
        // 创建一个集合，存放所有下单了的 商品 id
        List<String> skuIds = new ArrayList<>();

        // 获取购物车添加到订单中的商品 skuId
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        for (OrderDetail orderDetail : orderDetailList) {
            Long skuId = orderDetail.getSkuId();
            skuIds.add(skuId.toString());
        }
        cartFeignClient.deleteSkuFromCart(skuIds);

        // 2.发送一个请求
        rabbitTemplate.convertAndSend(RabbitConst.ORDER_EXCHANGE, RabbitConst.CREATE_ORDER_RK, orderInfo.getId());
    }
}
